﻿using System;
using System.Xml;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;

using Weazel.Gezel.Model;

namespace Weazel.Gezel.Exporter
{
  internal class ControllerExporter
  {
    /// <summary>
    /// Convert a gezel controller to
    /// its xml representation.
    /// </summary>
    /// <param name="xmlDoc">recieves output</param>
    /// <param name="parent">parent xml element</param>
    /// <param name="controller">controller to convert</param>
    internal static void ToXml(XmlDocument xmlDoc, XmlElement parent, Controller controller) 
    {
      // dont output controllers for instantiated datapaths
      if (controller.Datapath.ClonedFromDatapath != null)
        return;

      XmlElement controllerElement = xmlDoc.CreateElement("Controller");
      controllerElement.SetAttribute("name", controller.LocalName);
      controllerElement.SetAttribute("id", controller.Name);
      controllerElement.SetAttribute("datapath", controller.DatapathRef);

      if (controller.Type == Controller.ControllerType.Fsm)
      {
        fsmToXml(xmlDoc, controller, controllerElement);
      }
      else if (controller.Type == Controller.ControllerType.Sequencer)
      {
        sequencerToXml(xmlDoc, parent, controller, controllerElement);
      }
      else // hardwired controller
      {
        Trace.Assert(controller.Type == Controller.ControllerType.Hardwired);

        hardwiredToXml(xmlDoc, controller, controllerElement);
      }

      parent.AppendChild(controllerElement);
    }

    /// <summary>
    /// Convert a sequencer controller to
    /// its xml representation.
    /// </summary>
    /// <param name="xmlDoc">recieves output</param>
    /// <param name="controller">controller to convert</param>
    /// <param name="controllerElement">xml element to append children on</param>
    private static void sequencerToXml(XmlDocument xmlDoc, XmlElement parent, Controller controller, XmlElement controllerElement)
    {
      XmlElement initialStateElement = xmlDoc.CreateElement("State");
      initialStateElement.SetAttribute("name", "state_" + controller.SignalFlowGraphRefs[0]);
      initialStateElement.SetAttribute("initial", "true");

      parent.AppendChild(initialStateElement);

      for (int i = 1; i < controller.SignalFlowGraphRefs.Count; i++)
      {
        XmlElement stateElement = xmlDoc.CreateElement("State");
        stateElement.SetAttribute("name", "state_" + controller.SignalFlowGraphRefs[i]);
        stateElement.SetAttribute("initial", "false");

        controllerElement.AppendChild(stateElement);
      }

      for (int i = 0; i < controller.SignalFlowGraphRefs.Count; i++)
      {
        XmlElement transitionSetElement = xmlDoc.CreateElement("TransitionSet");
        int nextIndex = i % controller.SignalFlowGraphRefs.Count;
        transitionSetElement.SetAttribute("source", "state_" + controller.SignalFlowGraphRefs[i]);

        XmlElement transitionElement = xmlDoc.CreateElement("Transition");
        transitionElement.SetAttribute("target", "state_" + controller.SignalFlowGraphRefs[nextIndex]);

        XmlElement sfgRefElement = xmlDoc.CreateElement("SignalFlowGraphRef");
        sfgRefElement.SetAttribute("name", controller.SignalFlowGraphRefs[nextIndex]);

        transitionElement.AppendChild(sfgRefElement);
        transitionSetElement.AppendChild(transitionElement);
        controllerElement.AppendChild(transitionSetElement);
      }
    }

    /// <summary>
    /// Convert a hardwired controller to
    /// its xml representation.
    /// </summary>
    /// <param name="xmlDoc">recieves output</param>
    /// <param name="controller">controller to convert</param>
    /// <param name="controllerElement">xml element to append children on</param>
    private static void hardwiredToXml(XmlDocument xmlDoc, Controller controller, XmlElement controllerElement)
    {
      const string dummyStateName = "dummy_state";

      XmlElement dummyStateElement = xmlDoc.CreateElement("State");
      dummyStateElement.SetAttribute("name", dummyStateName);
      dummyStateElement.SetAttribute("initial", "true");

      controllerElement.AppendChild(dummyStateElement);

      XmlElement transitionSetElement = xmlDoc.CreateElement("TransitionSet");
      transitionSetElement.SetAttribute("source", dummyStateName);

      XmlElement transitionElement = xmlDoc.CreateElement("Transition");
      transitionElement.SetAttribute("target", dummyStateName);

      XmlElement sfgRefElement = xmlDoc.CreateElement("SignalFlowGraphRef");
      sfgRefElement.SetAttribute("name", controller.SignalFlowGraphRefs[0]);

      transitionElement.AppendChild(sfgRefElement);
      transitionSetElement.AppendChild(transitionElement);
      controllerElement.AppendChild(transitionSetElement);
    }

    /// <summary>
    /// Convert a finite state machine controller to
    /// its xml representation.
    /// </summary>
    /// <param name="xmlDoc">recieves output</param>
    /// <param name="controller">controller to convert</param>
    /// <param name="controllerElement">xml element to append children on</param>
    private static void fsmToXml(XmlDocument xmlDoc, Controller controller, XmlElement controllerElement)
    {
      foreach (State state in controller.States.Values)
      {
        stateToXml(xmlDoc, controllerElement, state, state == controller.InitialState);
      }

      Queue<string> conditionQueue = new Queue<string>();
      foreach (Transition transition in controller.Transitions.Values)
      {
        XmlElement transitionSetElement = xmlDoc.CreateElement("TransitionSet");

        XmlAttribute sourceAttribute = xmlDoc.CreateAttribute("source");
        sourceAttribute.Value = transition.SourceState;
        transitionSetElement.Attributes.Append(sourceAttribute);

        conditionQueue.Clear();
        transitionToXml(xmlDoc, transitionSetElement, transition, conditionQueue);
        Trace.Assert(conditionQueue.Count == 0);

        controllerElement.AppendChild(transitionSetElement);
      }
    }

    /// <summary>
    /// Recursively process transition and output
    /// non-nested list of transition xml elements
    /// with appropriate conditions. A condition 
    /// queue is used to transfer conditions to
    /// simple transitions lower in the hiearchy.
    /// </summary>
    /// <param name="xmlDoc"></param>
    /// <param name="parent"></param>
    /// <param name="transition"></param>
    /// <param name="conditionQueue"></param>
		private static void transitionToXml(XmlDocument xmlDoc, XmlElement parent, Transition transition, Queue<string> conditionQueue)
		{
			if (transition.TransitionType == TransitionType.SimpleUnconditional)
			{
				Trace.Assert(transition.SecondTargetSfgs == null);
				Trace.Assert(transition.SecondTargetState == null);

				XmlElement transitionElement = xmlDoc.CreateElement("Transition");

				XmlAttribute targetAttribute = xmlDoc.CreateAttribute("target");
				targetAttribute.Value = transition.FirstTargetState;
				transitionElement.Attributes.Append(targetAttribute);

				if (conditionQueue.Count > 0)
				{
					Trace.Assert(conditionQueue.Count == 1);
					XmlAttribute conditionAttribute = xmlDoc.CreateAttribute("condition");
					conditionAttribute.Value = conditionQueue.Dequeue();

					transitionElement.Attributes.Append(conditionAttribute);
				}

				foreach (string sfgRef in transition.FirstTargetSfgs)
				{
					XmlElement sfgRefElement = xmlDoc.CreateElement("SignalFlowGraphRef");

					XmlAttribute nameAttribute = xmlDoc.CreateAttribute("name");
					nameAttribute.Value = sfgRef;
					sfgRefElement.Attributes.Append(nameAttribute);

					transitionElement.AppendChild(sfgRefElement);

					parent.AppendChild(transitionElement);
				}
			}
			else if (transition.TransitionType == TransitionType.SimpleConditional)
			{
				Trace.Assert(transition.FirstTargetState != null);
				Trace.Assert(transition.SecondTargetState != null);
				Trace.Assert(transition.FirstTargetSfgs != null);
				Trace.Assert(transition.SecondTargetSfgs != null);

				//Trace.Assert(conditionQueue.Count == 0);

				XmlElement firstTransitionElement = xmlDoc.CreateElement("Transition");

				XmlAttribute conditionAttribute = xmlDoc.CreateAttribute("condition");
				conditionAttribute.Value = transition.Condition.ToString();
				firstTransitionElement.Attributes.Append(conditionAttribute);

				XmlAttribute firstTargetAttribute = xmlDoc.CreateAttribute("target");
				firstTargetAttribute.Value = transition.FirstTargetState;
				firstTransitionElement.Attributes.Append(firstTargetAttribute);

				foreach (string sfgRef in transition.FirstTargetSfgs)
				{
					XmlElement sfgRefElement = xmlDoc.CreateElement("SignalFlowGraphRef");

					XmlAttribute nameAttribute = xmlDoc.CreateAttribute("name");
					nameAttribute.Value = sfgRef;
					sfgRefElement.Attributes.Append(nameAttribute);

					firstTransitionElement.AppendChild(sfgRefElement);
				}

				parent.AppendChild(firstTransitionElement);

				XmlElement secondTransitionElement = xmlDoc.CreateElement("Transition");

				XmlAttribute secondTargetAttribute = xmlDoc.CreateAttribute("target");
				secondTargetAttribute.Value = transition.SecondTargetState;
				secondTransitionElement.Attributes.Append(secondTargetAttribute);
				
				foreach (string sfgRef in transition.SecondTargetSfgs)
				{
					XmlElement sfgRefElement = xmlDoc.CreateElement("SignalFlowGraphRef");

					XmlAttribute nameAttribute = xmlDoc.CreateAttribute("name");
					nameAttribute.Value = sfgRef;
					sfgRefElement.Attributes.Append(nameAttribute);

					secondTransitionElement.AppendChild(sfgRefElement);
				}

				parent.AppendChild(secondTransitionElement);
			}
			else if (transition.TransitionType == TransitionType.Nested) 
			{
				Trace.Assert(transition.FirstTargetState == null);
				Trace.Assert(transition.SecondTargetState == null);
				Trace.Assert(transition.FirstTargetSfgs == null);
				Trace.Assert(transition.SecondTargetSfgs == null);

				conditionQueue.Enqueue(transition.Condition.ToString());

				transitionToXml(xmlDoc, parent, transition.FirstTransition, conditionQueue);
        transitionToXml(xmlDoc, parent, transition.SecondTransition, conditionQueue);
			}			
		}

    /// <summary>
    /// Outputs the xml for a fsm state.
    /// </summary>
    /// <param name="xmlDoc"></param>
    /// <param name="parent"></param>
    /// <param name="state"></param>
    /// <param name="isInitial"></param>
		private static void stateToXml(XmlDocument xmlDoc, XmlElement parent, State state, bool isInitial)
		{
			XmlElement stateElement = xmlDoc.CreateElement("State");

			XmlAttribute nameAttribute = xmlDoc.CreateAttribute("name");
			nameAttribute.Value = state.LocalName;
			stateElement.Attributes.Append(nameAttribute);

			XmlAttribute idAttribute = xmlDoc.CreateAttribute("id");
			idAttribute.Value = state.Name;
			stateElement.Attributes.Append(idAttribute);

			if (isInitial)
			{
				XmlAttribute initialAttribute = xmlDoc.CreateAttribute("initial");
				initialAttribute.Value = "true";
				stateElement.Attributes.Append(initialAttribute);
			}
			else
			{
				XmlAttribute initialAttribute = xmlDoc.CreateAttribute("initial");
				initialAttribute.Value = "false";
				stateElement.Attributes.Append(initialAttribute);
			}

			parent.AppendChild(stateElement);
		}

    /// <summary>
    /// Converts a controller object to gezel.
    /// </summary>
    /// <param name="textWriter"></param>
    /// <param name="controller"></param>
    internal static void ToGezel(System.CodeDom.Compiler.IndentedTextWriter textWriter, Controller controller)
    {
      // dont output controllers for instantiated datapaths
      if (controller.Datapath.ClonedFromDatapath != null)
        return;

      if (controller.Type == Controller.ControllerType.Hardwired)
      {
        textWriter.WriteLine("hardwired {0}({1}) {{ {2}; }}", 
          controller.LocalName, 
          controller.DatapathRef, 
          controller.SignalFlowGraphRefs[0]
        );
				textWriter.WriteLine();
      }
      else if (controller.Type == Controller.ControllerType.Sequencer)
      {
        StringBuilder buffer = new StringBuilder();
        foreach (String sfgRef in controller.SignalFlowGraphRefs)
        {
          buffer.AppendFormat("{0}; ", sfgRef);
        }

        buffer.Remove(buffer.Length - 2, 2); // remote last to chars

        textWriter.WriteLine("sequencer {0}({1}) {{ {2} }}",
          controller.LocalName,
          controller.DatapathRef,
          buffer.ToString()
        );
				textWriter.WriteLine();
      }
      else
      {
        Trace.Assert(controller.Type == Controller.ControllerType.Fsm);

				fsmToGezel(textWriter, controller);
      }
    }

    /// <summary>
    /// Convert a finite state machine controller to gezel.
    /// </summary>
    /// <param name="textWriter"></param>
    /// <param name="controller"></param>
		private static void fsmToGezel(System.CodeDom.Compiler.IndentedTextWriter textWriter, Controller controller)
		{
			textWriter.WriteLine("fsm {0}({1})", controller.Name, controller.DatapathRef);
			textWriter.WriteLine("{");

			textWriter.Indent++;

			textWriter.WriteLine("initial {0};", controller.InitialState.LocalName);

			foreach (State state in controller.States.Values)
			{
				if (state != controller.InitialState)
				{
					textWriter.WriteLine("state {0};", state.LocalName);
				}
			}
			
			Queue<string> conditionQueue = new Queue<string>();
			foreach (Transition transition in controller.Transitions.Values)
			{
				conditionQueue.Clear();
				bool isFirstTransition = true;
				transitionToGezel(textWriter, transition, conditionQueue, ref isFirstTransition);
			}

			textWriter.Indent--;

			textWriter.WriteLine("}");
			textWriter.WriteLine();
		}


    /// <summary>
    /// Recursively convert a transition hiearchy to its
    /// corresponding gezel representation. The method
    /// uses a condition queue to hold conditions from
    /// nested transitions which should be output for
    /// simple transitions lower in the hiearchy. The
    /// boolean records if the source state has been
    /// output which are used to output 'else' keywords
    /// when appropriate.
    /// </summary>
    /// <param name="textWriter">recieves output</param>
    /// <param name="transition">transition to convert</param>
    /// <param name="conditionQueue">queued transitions</param>
    /// <param name="isFirstTransition">true iff source state has not been output</param>
		private static void transitionToGezel(System.CodeDom.Compiler.IndentedTextWriter textWriter, Transition transition, Queue<string> conditionQueue, ref bool isFirstTransition)
		{
			if (transition.TransitionType == TransitionType.SimpleUnconditional)
			{
				Trace.Assert(transition.SecondTargetSfgs == null);
				Trace.Assert(transition.SecondTargetState == null);

				if (conditionQueue.Count > 0)
				{
					if (isFirstTransition)
					{
						textWriter.WriteLine(
              "@{0} if({1}) then ({2}) -> {3};", 
              transition.SourceState, 
              conditionQueue.Dequeue(), 
              toList(transition.FirstTargetSfgs), 
              transition.FirstTargetState
            );
						// mark that we have output the source state
            isFirstTransition = false;
					}
					else
					{
						textWriter.WriteLine(
              "else if({0}) then ({1}) -> {2};", 
              conditionQueue.Dequeue(), 
              toList(transition.FirstTargetSfgs), 
              transition.FirstTargetState
            );
					}
				}
				else
				{
					textWriter.WriteLine(
            "@{0} ({1}) -> {2};", 
            transition.SourceState, 
            toList(transition.FirstTargetSfgs), 
            transition.FirstTargetState
          );
				}
			}
			else if (transition.TransitionType == TransitionType.SimpleConditional)
			{
				Trace.Assert(transition.FirstTargetSfgs != null);
				Trace.Assert(transition.FirstTargetState != null);
				Trace.Assert(transition.SecondTargetSfgs != null);
				Trace.Assert(transition.SecondTargetState != null);

				Trace.Assert(conditionQueue.Count == 0);

				if (isFirstTransition)
				{
					textWriter.WriteLine(
            "@{0} if ({1}) then ({2}) -> {3};", 
            transition.SourceState, 
            transition.Condition, 
            toList(transition.FirstTargetSfgs), 
            transition.FirstTargetState
          );
          // mark that we have output the source state
          isFirstTransition = false;
				}
				else
				{
					textWriter.WriteLine(
            "else if ({1}) then ({2}) -> {3};", 
            transition.SourceState, 
            transition.Condition, 
            toList(transition.FirstTargetSfgs), 
            transition.FirstTargetState
          );
				}
				textWriter.WriteLine(
          "     else ({0}) -> {1};", 
          toList(transition.SecondTargetSfgs), 
          transition.SecondTargetState
        );
			}
			else if (transition.TransitionType == TransitionType.Nested)
			{
				Trace.Assert(transition.FirstTargetSfgs == null);
				Trace.Assert(transition.FirstTargetState == null);
				Trace.Assert(transition.SecondTargetSfgs == null);
				Trace.Assert(transition.SecondTargetState == null);

				conditionQueue.Enqueue(transition.Condition.ToString());

				transitionToGezel(
          textWriter, 
          transition.FirstTransition, 
          conditionQueue, 
          ref isFirstTransition
        );
				transitionToGezel(
          textWriter, 
          transition.SecondTransition, 
          conditionQueue, 
          ref isFirstTransition
        );
			}
			else
			{
				Trace.Fail("unknown transition type");
			}
		}

    /// <summary>
    /// Converts a string array to a comma-
    /// separated string. Used to write out
    /// lists of signal-flow graphs.
    /// </summary>
    /// <param name="strings">string array to convert</param>
    /// <returns></returns>
		private static string toList(string[] strings)
		{
			StringBuilder buffer = new StringBuilder();

			foreach (string str in strings)
			{
				buffer.AppendFormat("{0}, ", str);
			}

			buffer.Remove(buffer.Length - 2, 2);
			return buffer.ToString();
		}
  }
}
